package com.linjiahao.nettyGateway.config;


import com.linjiahao.nettyGateway.cluster.ClusterManager;
import com.linjiahao.nettyGateway.custom.CustomReactorNettyWebSocketClient;
import com.linjiahao.nettyGateway.loadBalancer.WebsocketLoadBalancerConfig;
import io.netty.channel.ChannelOption;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.embedded.NettyWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.embedded.netty.NettyServerCustomizer;
import org.springframework.cloud.gateway.config.*;
import org.springframework.cloud.gateway.filter.NettyRoutingFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.WebsocketRoutingFilter;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter;
import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
import org.springframework.web.reactive.socket.client.WebSocketClient;
import org.springframework.web.reactive.socket.server.WebSocketService;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
import reactor.netty.tcp.ProxyProvider;

import java.security.cert.X509Certificate;
import java.util.List;

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore({HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class})
@AutoConfigureAfter({GatewayReactiveLoadBalancerClientAutoConfiguration.class, GatewayClassPathWarningAutoConfiguration.class})
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayWebsocketConfiguration extends GatewayAutoConfiguration {

    private final Integer MAX_FRAME_PAYLOAD_LENGTH = 65536;



    @Bean
    public ClusterManager getClusterManager(){
        return new ClusterManager();
    }

/**
* 重载网关 websocket连接配置类
* */
    @Bean
    @Override
    public WebsocketRoutingFilter websocketRoutingFilter(@Qualifier("customReactorNettyWebSocketClient") WebSocketClient webSocketClient,
                                                         WebSocketService webSocketService, ObjectProvider<List<HttpHeadersFilter>> headersFilters) {

        return new WebsocketRoutingFilter(webSocketClient, webSocketService, headersFilters);
    }


    @Bean("customReactorNettyWebSocketClient")
    public CustomReactorNettyWebSocketClient reactorNettyWebSocketClient(HttpClientProperties properties, HttpClient httpClient,ClusterManager clusterManager) {
        CustomReactorNettyWebSocketClient webSocketClient = new CustomReactorNettyWebSocketClient(httpClient, clusterManager);
        if(properties.getWebsocket().getMaxFramePayloadLength() != null){
            webSocketClient.setMaxFramePayloadLength(properties.getWebsocket().getMaxFramePayloadLength());
        }else {
            webSocketClient.setMaxFramePayloadLength(MAX_FRAME_PAYLOAD_LENGTH);
        }
        return webSocketClient;
    }

    @Configuration(
            proxyBeanMethods = false
    )
    @ConditionalOnClass({HttpClient.class})
    protected static class NettyConfiguration {
        protected final Log logger = LogFactory.getLog(this.getClass());
        protected NettyConfiguration() {
        }

        @Bean
        @ConditionalOnProperty(
                name = {"spring.cloud.gateway.httpserver.wiretap"}
        )
        public NettyWebServerFactoryCustomizer nettyServerWiretapCustomizer(Environment environment, ServerProperties serverProperties) {
            return new NettyWebServerFactoryCustomizer(environment, serverProperties) {
                @Override
                public void customize(NettyReactiveWebServerFactory factory) {
                    factory.addServerCustomizers(new NettyServerCustomizer[]{(httpServer) -> {
                        return httpServer.wiretap(true);
                    }});
                    super.customize(factory);
                }
            };
        }

        @Bean
        @ConditionalOnMissingBean
        public HttpClient gatewayHttpClient(HttpClientProperties properties) {
            HttpClientProperties.Pool pool = properties.getPool();
            ConnectionProvider connectionProvider;
            if (pool.getType() == HttpClientProperties.Pool.PoolType.DISABLED) {
                connectionProvider = ConnectionProvider.newConnection();
            } else if (pool.getType() == HttpClientProperties.Pool.PoolType.FIXED) {
                connectionProvider = ConnectionProvider.fixed(pool.getName(), pool.getMaxConnections(), pool.getAcquireTimeout());
            } else {
                connectionProvider = ConnectionProvider.elastic(pool.getName());
            }

            HttpClient httpClient = HttpClient.create(connectionProvider).tcpConfiguration((tcpClient) -> {
                if (properties.getConnectTimeout() != null) {
                    tcpClient = tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, properties.getConnectTimeout());
                }

                HttpClientProperties.Proxy proxy = properties.getProxy();
                if (StringUtils.hasText(proxy.getHost())) {
                    tcpClient = tcpClient.proxy((proxySpec) -> {
                        ProxyProvider.Builder builder = proxySpec.type(ProxyProvider.Proxy.HTTP).host(proxy.getHost());
                        PropertyMapper map = PropertyMapper.get();
                        proxy.getClass();
                        map.from(proxy::getPort).whenNonNull().to(builder::port);
                        proxy.getClass();
                        map.from(proxy::getUsername).whenHasText().to(builder::username);
                        proxy.getClass();
                        map.from(proxy::getPassword).whenHasText().to((password) -> {
                            builder.password((s) -> {
                                return password;
                            });
                        });
                        proxy.getClass();
                        map.from(proxy::getNonProxyHostsPattern).whenHasText().to(builder::nonProxyHosts);
                    });
                }

                return tcpClient;
            });
            HttpClientProperties.Ssl ssl = properties.getSsl();
            if (ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0 || ssl.getTrustedX509CertificatesForTrustManager().length > 0 || ssl.isUseInsecureTrustManager()) {
                httpClient = httpClient.secure((sslContextSpec) -> {
                    SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();
                    X509Certificate[] trustedX509Certificates = ssl.getTrustedX509CertificatesForTrustManager();
                    if (trustedX509Certificates.length > 0) {
                        sslContextBuilder = sslContextBuilder.trustManager(trustedX509Certificates);
                    } else if (ssl.isUseInsecureTrustManager()) {
                        sslContextBuilder = sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
                    }

                    try {
                        sslContextBuilder = sslContextBuilder.keyManager(ssl.getKeyManagerFactory());
                    } catch (Exception var6) {
                        this.logger.error(var6);
                    }

                    sslContextSpec.sslContext(sslContextBuilder).defaultConfiguration(ssl.getDefaultConfigurationType()).handshakeTimeout(ssl.getHandshakeTimeout()).closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout()).closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout());
                });
            }

            if (properties.isWiretap()) {
                httpClient = httpClient.wiretap(true);
            }

            return httpClient;
        }

        @Bean
        public HttpClientProperties httpClientProperties() {
            return new HttpClientProperties();
        }

        @Bean
        public NettyRoutingFilter routingFilter(HttpClient httpClient, ObjectProvider<List<HttpHeadersFilter>> headersFilters, HttpClientProperties properties) {
            return new NettyRoutingFilter(httpClient, headersFilters, properties);
        }

        @Bean
        public NettyWriteResponseFilter nettyWriteResponseFilter(GatewayProperties properties) {
            return new NettyWriteResponseFilter(properties.getStreamingMediaTypes());
        }

        @Bean
        @ConditionalOnMissingBean(RoutePredicateHandlerMapping.class)
        public ReactorNettyWebSocketClient reactorNettyWebSocketClient(HttpClientProperties properties, HttpClient httpClient) {
            ReactorNettyWebSocketClient webSocketClient = new ReactorNettyWebSocketClient(httpClient);
            if (properties.getWebsocket().getMaxFramePayloadLength() != null) {
                webSocketClient.setMaxFramePayloadLength(properties.getWebsocket().getMaxFramePayloadLength());
            }

            return webSocketClient;
        }
    }



}
